package cn.good.yan.service.t5;

import cn.good.yan.mapper.UserMapper;
import cn.good.yan.service.t2.pojo.User;
import cn.good.yan.service.t5.bloom.CheckUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 5 布隆过滤器
 *
 * @author shengren.yan
 * @create 2023-11-26
 */
@Service
@Slf4j
public class BlService {

    public static void main(String[] args) {
        // 模拟hash冲突
        System.out.println("Aa".hashCode());
        System.out.println("BB".hashCode());

    }

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private CheckUtils checkUtils;

    public static final String CACHE_KEY_USER = "user:";

    // 该布隆过滤器的key名
    public static final String bKey = "whitelistCustomer";

    /**
     * 写操作
     *
     * @param user
     */
    public void addCustomer(User user) {
        int i = userMapper.insert(user);
        // 新曾成功
        if (i > 0) {
            User result = userMapper.selectById(user.getId());
            String key = CACHE_KEY_USER + user.getId();
            // 最新的对象，写入redis中
            redisTemplate.opsForValue().set(key, result);
        }
    }

    /**
     * 读操作  + 双检加锁  + null缓存
     */
    public User findCustomerById(Integer id) {
        User user = null;
        String key = CACHE_KEY_USER + id;
        // 1.去redis上查询缓存
        user = (User) redisTemplate.opsForValue().get(key);

        // 2. 如果redis有，直接返回  如果redis没有，在mysql上查询
        if (user == null) {
            // 3.对于高QPS的优化，进来就先加锁，保证一个请求操作，让外面的redis等待一下，避免击穿mysql
            synchronized (BlService.class) {
                // 3.1 第二次查询redis，加锁后
                user = (User) redisTemplate.opsForValue().get(key);
                // 4.mysql有，redis无
                if (user == null) {
                    // 5.再去查询我们的mysql
                    user = userMapper.selectById(id);
                    if (user == null) {
                        // 6.1 defaultNull 规定为redis查询为空、MySQL查询也没有，缓存一个defaultNull标识为空，以防缓存穿透
                        redisTemplate.opsForValue().set(key, "defaultNull", 7L, TimeUnit.DAYS);
                    } else {
                        // 6.2 把mysql查询到的数据会写到到redis， 保持双写一致性  7天过期
                        redisTemplate.opsForValue().set(key, user, 7L, TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }

    /**
     * 读操作 - 去缓存中获取值，如果没有再去数据库获取，更新回缓存中
     * 对于QPS <= 1000 可以使用
     * @param id 主键id
     * @return
     */
    public User findCustomerById2(Integer id) {
        User user = null;
        String key = CACHE_KEY_USER + id;
        // 1.去redis上查询
        user = (User) redisTemplate.opsForValue().get(key);
        // 2. 如果redis有，直接返回  如果redis没有，在mysql上查询
        if(user == null) {
            user = userMapper.selectById(id);
            if (user != null) {
                redisTemplate.opsForValue().set(key, user);
            }
        }
        return user;
    }


    /**
     * 布隆过滤器 + 双检加锁  注意单个服务，在分布式系统锁将锁不住
     *
     * @param id 主键id
     * @return
     */
    public User findCustomerById3(Integer id) {
        User user = null;
        String key = CACHE_KEY_USER + id;
        // 先去布隆过滤器查询 - 是否存在
        if (!checkUtils.checkWithBloomFilter(bKey, key)) {
            log.info("白名单没有此信息，不可以访问" + key);
            // 布隆过滤器没有，那一定没有值
            return null;
        }
        // 1.1 去redis上查询
        user = (User) redisTemplate.opsForValue().get(key);

        // 2. 如果redis有，直接返回  如果redis没有，在mysql上查询
        if (user == null) {
            // 3.对于高QPS的优化，进来就先加锁，保证一个请求操作，让外面的redis等待一下，避免击穿mysql（大公司的操作 ）
            synchronized (BlService.class) {
                // 3.1 第二次查询redis，加锁后
                user = (User) redisTemplate.opsForValue().get(key);

                // 4.mysql有，redis无
                if (user == null) {
                    // 5.再去查询我们的mysql
                    user = userMapper.selectById(id);
                    if (user == null) {
                        // 6.1 defaultNull 规定为redis查询为空、MySQL查询也没有，缓存一个defaultNull标识为空，以防缓存穿透
                        redisTemplate.opsForValue().set(key, "defaultNull", 7L, TimeUnit.DAYS);
                    } else {
                        // 6.2 把mysql查询到的数据会写到到redis， 保持双写一致性  7天过期
                        redisTemplate.opsForValue().set(key, user, 7L, TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }

}
